/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package abricots.entity.simulator;

import abricots.MyLog;
import abricots.entity.Entity;
import abricots.command.PlayerCommand;
import abricots.command.Command;
import abricots.entity.state.BasicEntityState;
import abricots.entity.state.EntityState;
import abricots.game.EntityFactory;
import com.esotericsoftware.minlog.Log;
import java.util.Iterator;
import java.util.List;

/**
 *
 * @author charly
 */
public abstract class BasicEntitySimulator implements EntitySimulator {
   protected final MyLog log = new MyLog(getClass().getSimpleName(), Log.LEVEL_DEBUG);
   protected final Entity entity;

   public BasicEntitySimulator(Entity entity) {
      this.entity = entity;
   }

   @Override
   public EntityState simulate(EntityState state, long targetGameTime, List<Command> commands) {
      log.trace("Asked to simulate from " + state.getGameTime() + " to " + targetGameTime);
      final Iterator<Command> it = commands.iterator();
      if (state == null) {
         return null;
      }
      if (state.getGameTime() >= targetGameTime) {
         return state;
      }
      BasicEntityState entityState = (BasicEntityState) state;
      BasicEntityState newState = null;
      PlayerCommand nextCommand = null;
      if (entityState == null) {
         entityState = (BasicEntityState) EntityFactory.buildStartState(entity.getEntityTypeId());
      }
      newState = (BasicEntityState) entityState.getCopy();

      long nextTime = entityState.getGameTime();
      // iterate to first interesting command
      while (it.hasNext()) {
         nextCommand = (PlayerCommand) it.next();
         if (nextCommand.getGameTime() >= nextTime) {
            break;
         }
      }
      // Simulate by steps so we apply commands at right time and we dont miss a key frame
      while (nextTime < targetGameTime) {
         nextTime = calcNextFrameTime(nextCommand, targetGameTime);
         newState = applyMovement(newState, (int) (nextTime - newState.getGameTime()));
         newState.setGameTime(nextTime);
         // We might have several commands at the same time
         while (nextCommand != null
                 && nextCommand.getGameTime() == nextTime) {
            newState = applyCommand(newState, nextCommand);
            if (it.hasNext()) {
               nextCommand = (PlayerCommand) it.next();
            } else {
               nextCommand = null;
            }
         }
      }
      return newState;
   }

   protected long calcNextFrameTime(Command command, long targetTime) {
      if (command == null) {
         return targetTime;
      }
      return Math.min(targetTime, command.getGameTime());
   }

   protected abstract BasicEntityState applyMovement(BasicEntityState state, int delta);

   protected abstract BasicEntityState applyCommand(BasicEntityState state, PlayerCommand command);
}
